package com.zrc.tt;


import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Predicate;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

public class Main {
    private static String ROOT;

    private static String SDK_PATH;
    private static String APK_PATH;
    private static String AAR_PATH;
    private static String PLUGIN_PATH;

    private static String KS_PATH;
    private static String KS_KEY_ALIAS;
    private static String KS_PASS;
    private static String KEY_PASS;

    public static void run(String[] args) throws Exception {
        init(args);//初始化操做
        modifyApkManifest();//动态修改清单文件
        makeDecodeDex();//1.制作只包含解密代码的dex文件
        encryptApkAllDex();//2.加密APK中所有的dex文件
        makeApk();//3.把dex放入apk解压目录，重新压成apk文件
        zipalign(); //4.对齐
        jksToApk();//5. 签名打包
    }

    private static void init(String[] args) {
        SDK_PATH = args[0];
        APK_PATH = args[1];
        AAR_PATH = args[2];
        PLUGIN_PATH = args[3];
        KS_PATH = args[4];
        KS_KEY_ALIAS = args[5];
        KS_PASS = args[6];
        KEY_PASS = args[7];

        String path1 = Main.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        File f1 = new File(path1);
        String path2 = f1.getParent();
        File f2 = new File(path2);
        String path3 = f2.getParent();
        File f3 = new File(path3);
        String path4 = f3.getParent();
        File f4 = new File(path4);
        String path5 = f4.getParent();
        File f5 = new File(path5);
        ROOT = f5.getParent();

        Utils.deleteFolders(Utils.linkPath(ROOT, "build", "aar_dir"));
        Utils.deleteFolders(Utils.linkPath(ROOT, "build", "app_dir"));
        Utils.deleteFolders(Utils.linkPath(ROOT, "build", "app-unsign-unalign.apk"));
        Utils.deleteFolders(Utils.linkPath(ROOT, "build", "app-unsign-align.apk"));
        Utils.deleteFolders(Utils.linkPath(ROOT, "build", "app-sign-align.apk"));

    }

    private static String modifyApkManifest() {
        File apkFile = new File(APK_PATH);
        String apktool = Utils.linkPath(ROOT, "libs", "apktool_2.7.0.jar");
        String outputPath = Utils.linkPath(ROOT, "build", "apk");
        String outApk = Utils.linkPath(ROOT, "build", Utils.getNameByPath(APK_PATH));
        try {
            //1：执行命令进行反编译原apk
            System.out.println("开始反编译原apk ......");
            boolean ret = Run.exec(String.format("java -jar %s " + "d -o " + outputPath + " " + apkFile.getAbsolutePath(), apktool));
            if (!ret) return null;

            //2.修改AndroidManifest.xml，使用壳的Application替换原Application,并将原Application名称配置在meta-data中
            File xmlFile = new File(outputPath, "AndroidManifest.xml");
            if (xmlFile == null && !xmlFile.exists()) {
                System.out.println("指定的AndroidManifest.xml文件不存在");
                return null;
            }
            //采用Dom读取AndroidManifest.xml文件
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //1.实例化Dom工厂

            DocumentBuilder builder = factory.newDocumentBuilder(); //2.构建一个builder
            Document document = builder.parse(xmlFile); //3.通过builder解析xml文件

            NodeList nodeList = document.getElementsByTagName("application");
            String shellClazzName = "com.zrc.ke.ProxyApplication";
            if (nodeList != null) {
                Node app = nodeList.item(0);
                //获取原APK中application
                String applicationName = "android.app.Application";
                NamedNodeMap attrMap = app.getAttributes();
                Node android_name = attrMap.getNamedItem("android:name");
                if (android_name != null) {
                    applicationName = android_name.getNodeValue();
                    android_name.setNodeValue(shellClazzName);
                } else {
                    Attr attr = document.createAttribute("android:name");
                    attr.setValue(shellClazzName);
                    attrMap.setNamedItem(attr);
                }

                Node testOnly = attrMap.getNamedItem("android:testOnly");
                if (testOnly != null) {
                    testOnly.setNodeValue("false");
                } else {//不存在该属性时，则创建一个
                    Attr attr = document.createAttribute("android:testOnly");
                    attr.setValue("false");
                    attrMap.setNamedItem(attr);
                }

                //删除AndroidManifest程序入口
                NodeList appchild = app.getChildNodes();
                for (int i = 0; i < appchild.getLength(); i++) {
                    Node node = appchild.item(i);
                    NamedNodeMap ddd = node.getAttributes();
                    if (ddd == null) continue;
                    for (int i1 = 0; i1 < ddd.getLength(); i1++) {
                        if (ddd.item(i1).getNodeValue().equals("com.cocos.game.AppActivity")) {
                            app.removeChild(node);
                            break;
                        }
                    }
                }

                //添加新的主程序入口activity
                Element action = document.createElement("action");
                action.setAttribute("android:name", "android.intent.action.MAIN");

                Element category = document.createElement("category");
                category.setAttribute("android:name", "android.intent.category.LAUNCHER");

                Element intent_filter = document.createElement("intent-filter");
                intent_filter.appendChild(action);
                intent_filter.appendChild(category);

                Element mian_activity = document.createElement("activity");
                mian_activity.setAttribute("android:name", "com.zrc.ke.DemoActivity");
                mian_activity.setAttribute("android:exported", "true");
                //mian_activity.setAttribute("android:label", "@string/app_name");
                //mian_activity.setAttribute("android:launchMode", "singleTask");
                mian_activity.setAttribute("android:screenOrientation", "landscape");
                mian_activity.setAttribute("android:theme", "@android:style/Theme.NoTitleBar.Fullscreen");
                mian_activity.appendChild(intent_filter);
                app.appendChild(mian_activity);

                Element activity = document.createElement("activity");
                activity.setAttribute("android:name", "com.cocos.game.AppActivity");
                activity.setAttribute("android:exported", "true");
                activity.setAttribute("android:label", "@string/app_name");
                activity.setAttribute("android:launchMode", "singleTask");
                activity.setAttribute("android:screenOrientation", "landscape");
                activity.setAttribute("android:theme", "@android:style/Theme.NoTitleBar.Fullscreen");
                app.appendChild(activity);

                Element proxyactivity = document.createElement("activity");
                proxyactivity.setAttribute("android:name", "com.zrc.ke.core.ProxyActivity");
                proxyactivity.setAttribute("android:exported", "true");
                app.appendChild(proxyactivity);


                //添加<meta-data>数据，记录原APK的application
                Element meta_data = document.createElement("meta-data");
                meta_data.setAttribute("android:name", "app_name");
                meta_data.setAttribute("android:value", applicationName);
                app.appendChild(meta_data);

                Element meta_data_1 = document.createElement("meta-data");
                meta_data_1.setAttribute("android:name", "app_version");
                meta_data_1.setAttribute("android:value", "1.0.0");
                app.appendChild(meta_data_1);

                //重新写入文件xml文件
                TransformerFactory outFactory = TransformerFactory.newInstance();
                Transformer transformer = outFactory.newTransformer();
                Source xmlSource = new DOMSource(document);
                Result outResult = new StreamResult(xmlFile);
                transformer.transform(xmlSource, outResult);
            }

            //3：开始回编译apk ...... 重新编译成apk,仍以原来名称命名
            ret = Run.exec(String.format("java -jar %s " + "b -o %s %s", apktool, outApk, outputPath));
            if (!ret) return null;
            Utils.deleteFolders(outputPath); //删除解压的目录
        } catch (ParserConfigurationException | TransformerException | SAXException | IOException e) {
            e.printStackTrace();
            return null;
        }
        return outApk;
    }

    public static void makeDecodeDex() {
        System.out.println("第1步：制作只包含解密代码的dex文件[开始]");
        File aarFile = new File(AAR_PATH);
        File aarTemp = new File(Utils.linkPath(ROOT, "build", "aar_dir"));
        Zip4jUtil.unzip(aarFile, aarTemp);
        File classesJar = new File(aarTemp, "classes.jar");
        File classesDex = new File(aarTemp, "classes.zip");
        String d8 = "d8" + (Run.isWindows() ? ".bat" : "") + " --min-api 26 --output ";
        String cmd = Utils.linkPath(SDK_PATH, d8) + classesDex.getAbsolutePath() + " " + classesJar.getAbsolutePath();
        Run.exec(cmd);
        Zip4jUtil.unzip(classesDex, aarTemp);
        Utils.deleteFolders(classesDex.getAbsolutePath());
        if (!classesDex.exists()) {
            System.out.println("dex，文件不存在，请排查...");
            return;
        }
        System.out.println("第1步：制作只包含解密代码的dex文件[完成]");
    }

    public static void encryptApkAllDex() throws Exception {
        System.out.println("2.加密APK中所有的dex文件[开始]");
        File apkFile = new File(APK_PATH);
        File apkTemp = new File(Utils.linkPath(ROOT, "build", "app_dir"));
        Zip4jUtil.unzip(apkFile, apkTemp);
        Utils.deleteFolders(Utils.linkPath(ROOT, "build", "app_dir", "META-INF"));
        //只要dex文件拿出来加密
        File[] dexFiles = apkTemp.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.endsWith(".dex");
            }
        });
        //AES加密了
        for (File dexFile : dexFiles) {
            byte[] bytes = Utils.getBytes(dexFile);
            byte[] encrypt = AESUtils.encrypt(bytes);
            FileOutputStream fos = new FileOutputStream(new File(apkTemp, "secret-" + dexFile.getName()));
            fos.write(encrypt);
            fos.flush();
            fos.close();
            dexFile.delete();
        }
        System.out.println("2.加密APK中所有的dex文件[完成]");
    }

    private static void makeApk() throws Exception {
        String apk = Utils.linkPath(ROOT, "build", Utils.getNameByPath(APK_PATH));
        Zip4jUtil.deleteDexFromZip(apk);
        File aarTemp = new File(Utils.linkPath(ROOT, "build", "aar_dir"));
        File[] dexs1 = aarTemp.listFiles((file, s) -> s.endsWith(".dex"));
        for (int i = 0; i < dexs1.length; i++) {
            Zip4jUtil.addFile2Zip(apk, dexs1[i].getPath(), "");
        }
        File apkTemp = new File(Utils.linkPath(ROOT, "build", "app_dir"));
        File[] dexs2 = apkTemp.listFiles((file, s) -> s.endsWith(".dex"));
        for (int i = 0; i < dexs2.length; i++) {
            Zip4jUtil.addFile2Zip(apk, dexs2[i].getPath(), "");
        }
        if (Utils.exists(PLUGIN_PATH)) {
            Zip4jUtil.addFile2Zip(apk, PLUGIN_PATH, "assets");
        } else {
            System.out.println("嵌入的插件apk不存在...");
        }

        //拷贝aar中的so库
        File file_armeabi_v7a=new File(Utils.linkPath(aarTemp.getPath(),"jni/armeabi-v7a"));
        if (file_armeabi_v7a.exists()){
            for (File listFile : file_armeabi_v7a.listFiles()) {
                Zip4jUtil.addFile2Zip(apk, listFile.getPath(), "lib/armeabi-v7a");
            }
        }

        File file_arm64_v8a=new File(Utils.linkPath(aarTemp.getPath(),"jni/arm64-v8a"));
        if (file_arm64_v8a.exists()){
            for (File listFile : file_arm64_v8a.listFiles()) {
                Zip4jUtil.addFile2Zip(apk, listFile.getPath(), "lib/arm64-v8a");
            }
        }

        File file_x86=new File(Utils.linkPath(aarTemp.getPath(),"jni/x86"));
        if (file_x86.exists()){
            for (File listFile : file_x86.listFiles()) {
                Zip4jUtil.addFile2Zip(apk, listFile.getPath(), "lib/x86");
            }
        }

        File file_x86_64=new File(Utils.linkPath(aarTemp.getPath(),"jni/x86_64"));
        if (file_x86_64.exists()){
            for (File listFile : file_x86.listFiles()) {
                Zip4jUtil.addFile2Zip(apk, listFile.getPath(), "lib/x86_64");
            }
        }

        Utils.deleteFolders(aarTemp.getAbsolutePath());
        Utils.deleteFolders(apkTemp.getAbsolutePath());
    }


    private static void zipalign() {
        System.out.println("4. 对齐[开始]");
        File unSignedApk = new File(Utils.linkPath(ROOT, "build", Utils.getNameByPath(APK_PATH)));
        File alignedApk = new File(Utils.linkPath(ROOT, "build", "app-unsign-align.apk"));
        String cmd = Utils.linkPath(SDK_PATH, "zipalign" + (Run.isWindows() ? ".exe" : "") + " -f -v -p  4 " + unSignedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath());
        Run.exec(cmd);
        System.out.println("4. 对齐[完成]");
    }

    public static void jksToApk() {
        System.out.println("5. 签名 打包[开始]");
        File signedApk = new File(Utils.linkPath(ROOT, "build", "app-sign-align.apk"));
        File jks = new File(KS_PATH);
        File alignedApk = new File(Utils.linkPath(ROOT, "build", "app-unsign-align.apk"));
        String cmd = Utils.linkPath(SDK_PATH, "apksigner" + (Run.isWindows() ? ".bat" : "") + " sign --ks ") + jks.getAbsolutePath() + " --ks-key-alias " + KS_KEY_ALIAS + " --ks-pass pass:" + KS_PASS + " --key-pass pass:" + KEY_PASS + " --v1-signing-enabled " + true + " --v2-signing-enabled " + true + " --v3-signing-enabled " + false + " --v4-signing-enabled " + false + " --out " + signedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath();
        Run.exec(cmd);

        System.out.println("查看签名后的apk信息...");
        String cmd1 = Utils.linkPath(SDK_PATH, "apksigner" + (Run.isWindows() ? ".bat" : "") + " verify -v --print-certs ") + signedApk.getAbsolutePath();
        Run.exec(cmd1);

        System.out.println("5. 签名 打包[完成]");
        System.out.println("打包链接：" + signedApk.getAbsolutePath());
    }
}